package net.gdface.facelog;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static net.gdface.facelog.SecurityChecks.checkSecurity;
import static net.gdface.facelog.FeatureConfig.*;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.primitives.Bytes;

import gu.simplemq.redis.JedisPoolLazy;
import gu.simplemq.redis.JedisUtils;
import gu.simplemq.redis.RedisFactory;
import gu.simplemq.redis.RedisTable;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;
import net.gdface.facelog.db.Constant;
import net.gdface.facelog.db.DeviceBean;
import net.gdface.facelog.db.DeviceGroupBean;
import net.gdface.facelog.db.FeatureBean;
import net.gdface.facelog.db.ImageBean;
import net.gdface.facelog.db.LogBean;
import net.gdface.facelog.db.PermitBean;
import net.gdface.facelog.db.PersonBean;
import net.gdface.facelog.db.PersonGroupBean;
import net.gdface.facelog.ServiceSecurityException.SecurityExceptionType;
import net.gdface.facelog.Token.TokenType;
import net.gdface.utils.BinaryUtils;

/**
 * 令牌管理模块
 * @author guyadong
 *
 */
class TokenMangement implements ServiceConstant,Constant {
	/**
	 * 由设备填写的常量字段
	 */
	private static final int[] CONST_FIELDS = {
			FL_DEVICE_ID_PRODUCT_NAME,
			FL_DEVICE_ID_MODEL,
			FL_DEVICE_ID_VENDOR,
			FL_DEVICE_ID_MANUFACTURER,
			FL_DEVICE_ID_MADE_DATE,
			FL_DEVICE_ID_VERSION,
			FL_DEVICE_ID_USED_SDKS,
			FL_DEVICE_ID_SERIAL_NO,
			FL_DEVICE_ID_EXT_BIN,
			FL_DEVICE_ID_EXT_TXT,
			FL_DEVICE_ID_STATUS};
	private static final String ACK_PREFIX = "ack_";
	private final DaoManagement dao;
	private final CryptographGenerator cg;
	/**  {@code 设备ID -> token} 映射表 */
	private final RedisTable<Token> deviceTokenTable;
	/**  {@code 人员ID -> token} 映射表 */
	private final RedisTable<Token> personTokenTable;
	/** {@code cmd sn -> 人员ID} */
	private RedisTable<Integer> cmdSnTable;
	/** {@code channel -> 人员ID} */
	private RedisTable<Integer> ackChannelTable;
	/** 是否执行设备令牌验证 */
	private final boolean validateDeviceToken;
	/** 是否执行人员令牌验证 */
	private final boolean validatePersonToken;
	/** 人员令牌失效时间(分钟) */
	private final int personTokenExpire;
	/** 是否拒绝普通人员申请令牌 */
	private final boolean rejectZero;
	/** 是否执行权限检查 */
	private final boolean checkPermission;
	/**
	 * @param dao
	 */
	TokenMangement(DaoManagement dao) {
		this.dao = checkNotNull(dao,"dao is null");
		this.cg = this.dao.getCryptographGenerator();
		this.rejectZero = CONFIG.getBoolean(TOKEN_PERSON_REJECTZERO);
		this.validateDeviceToken = CONFIG.getBoolean(TOKEN_DEVICE_VALIDATE);
		this.validatePersonToken = CONFIG.getBoolean(TOKEN_PERSON_VALIDATE);
		this.personTokenExpire =CONFIG.getInt(TOKEN_PERSON_EXPIRE);
		this.deviceTokenTable =  RedisFactory.getTable(TABLE_DEVICE_TOKEN, JedisPoolLazy.getDefaultInstance());
		this.personTokenTable =  RedisFactory.getTable(TABLE_PERSON_TOKEN, JedisPoolLazy.getDefaultInstance());
		this.deviceTokenTable.setKeyHelper(Token.KEY_HELPER);
		this.personTokenTable.setKeyHelper(Token.KEY_HELPER);
		this.personTokenTable.setExpire(personTokenExpire, TimeUnit.MINUTES);
		this.cmdSnTable =  RedisFactory.getTable(TABLE_CMD_SN, JedisPoolLazy.getDefaultInstance());
		this.ackChannelTable =  RedisFactory.getTable(TABLE_ACK_CHANNEL, JedisPoolLazy.getDefaultInstance());
		this.cmdSnTable.setExpire(CONFIG.getInt(TOKEN_CMD_SERIALNO_EXPIRE), TimeUnit.SECONDS);
		this.ackChannelTable.setExpire(CONFIG.getInt(TOKEN_CMD_ACKCHANNEL_EXPIRE), TimeUnit.SECONDS);
		this.checkPermission = CONFIG.getBoolean(SECURITY_CHECKPERMISSION);
		GlobalConfig.logTokenParameters();
	}
	/** 验证MAC地址是否有效(HEX格式,12字符,无分隔符,不区分大小写) */
	protected static final boolean isValidMac(String mac){
		return !Strings.isNullOrEmpty(mac) && mac.matches("^[a-fA-F0-9]{12}$");
	}
	protected static final void checkValidMac(String mac) throws ServiceSecurityException{
		if(!isValidMac(mac)){
			throw new ServiceSecurityException(String.format("INVALID MAC:%s ", mac))
			.setType(SecurityExceptionType.INVALID_MAC);
		}
	}
	/** 验证序列号是否有效 */
	protected boolean isValidSerialNo(String sn){
		return true;
	}
	/** 序列号已经被占用则抛出异常 */
	protected void checkNotOccupiedSerialNo(String sn) throws ServiceSecurityException{
		DeviceBean bean = null == sn ? null : dao.daoGetDeviceByIndexSerialNo(sn);
		if(null != bean){
			throw new ServiceSecurityException(
					String.format("serian no:%s be occupied by device ID[%d] MAC[%s]", sn,bean.getId(),bean.getMac()))
					.setType(SecurityExceptionType.OCCUPIED_SN).setDeviceID(bean.getId());
		}			
	}
	/** 序列号无效则抛出异常 */
	protected void checkValidSerialNo(String sn) throws ServiceSecurityException{
		if(!isValidSerialNo(sn)){
			throw new ServiceSecurityException(String.format("INVALID serial number:%s", sn))
					.setType(SecurityExceptionType.INVALID_SN);
		}
	}
	/** 允许的令牌类型 */
	enum Enable{
		/** 允许所有令牌类型 */ALL,
		/** 只允许人员令牌 */PERSON_ONLY,
		/** 只允许设备令牌 */DEVICE_ONLY,
		/** 只允许root令牌 */ROOT_ONLY;
		
		boolean isValid(TokenMangement tm,Token token){
			TokenOp.VALIDATE.asContextTokenOp();
			switch(this){
			case PERSON_ONLY:
				return tm.isValidPersonToken(token) || tm.isValidRootToken(token);
			case DEVICE_ONLY:
				return tm.isValidDeviceToken(token);
			case ROOT_ONLY:
				return tm.isValidRootToken(token);
			case ALL:
				return tm.isValidPersonToken(token) || tm.isValidDeviceToken(token) || tm.isValidRootToken(token);
			default:
				return false;
			}
		}
		/** 
		 * 验证令牌是否有效,无效抛出异常
		 * @throws ServiceSecurityException 
		 */
		void check(TokenMangement tm,Token token) throws ServiceSecurityException{
			if(isValid(tm,token)){
				return;
			}
			StringBuffer message = new StringBuffer("INVALID TOKEN");
			if(null != token){
				switch(this){
				case PERSON_ONLY:
					message.append(",Person Token required");
					break;
				case DEVICE_ONLY:
					message.append(",Device Token required");
					break;
				case ROOT_ONLY:
					message.append(",root Token required");
					break;
				default:
					break;
				}
			}else{
				message.append(",null token");
			}
			throw new ServiceSecurityException(message.toString())
								.setType(SecurityExceptionType.INVALID_TOKEN);
		}
	}
	boolean isUserToken(Token token){
		return token != null && (TokenType.PERSON.equals(token.getType()) || TokenType.ROOT.equals(token.getType()));
	}
	boolean isDeviceToken(Token token){
		return token != null && TokenType.DEVICE.equals(token.getType()) ;
	}
	boolean isRootToken(Token token){
		return token != null && TokenType.ROOT.equals(token.getType()) ;
	}
	boolean isPersonToken(Token token){
		return token != null && TokenType.PERSON.equals(token.getType()) ;
	}
	/** 验证设备令牌是否有效 */
	boolean isValidDeviceToken(Token token){
		if(validateDeviceToken){
			return null == token 
					? false 
					: TokenType.DEVICE.equals(token.getType()) && token.equals(deviceTokenTable.get(Integer.toString(token.getId())));			
		}else{
			return true;
		}
	}
	/** 验证PERSON/ROOT令牌是否有效 */
	boolean isValidUserToken(Token token){
		if(validatePersonToken){
			return null == token 
					? false 
					: (TokenType.PERSON.equals(token.getType()) || TokenType.ROOT.equals(token.getType()))
						&& token.equals(personTokenTable.get(Integer.toString(token.getId())));
		}else{
			return true;
		}
	}
	/** 验证人员令牌是否有效 */
	boolean isValidPersonToken(Token token){
		if(validatePersonToken){
			return token != null  ? TokenType.PERSON.equals(token.getType()) && isValidUserToken(token) : false;
		}else{
			return true;
		}
	}
	/** 验证root令牌是否有效 */
	boolean isValidRootToken(Token token){
		if(validatePersonToken){
			return TokenType.ROOT.equals(token.getType()) && isValidUserToken(token);
		}else{
			return true;
		}
	}
	/** 验证令牌是否有效 */
	boolean isValidToken(Token token){
		if(token != null){
			switch (token.getType()) {
			case DEVICE:
				return isValidDeviceToken(token);
			case PERSON:
			case ROOT:
				return isValidUserToken(token);
			default:
				break;
			}
		}
		return false;

	}
	/** 检查数据库是否存在指定的设备记录,没有则抛出异常{@link ServiceSecurityException} */
	protected void checkValidDeviceId(Integer deviceId) throws ServiceSecurityException{
		if(!this.dao.daoExistsDevice(deviceId)){
			throw new ServiceSecurityException(String.format("NOT EXISTS device %d", deviceId))
					.setType(SecurityExceptionType.INVALID_DEVICE_ID);
		}
	}
	private static Token makeToken(byte[] source){
		ByteBuffer buffer = ByteBuffer.wrap(new byte[8]);
		buffer.asLongBuffer().put(System.nanoTime());
		byte[] md5 = BinaryUtils.getMD5(Bytes.concat(checkNotNull(source),buffer.array()));
		ByteBuffer byteBuffers = ByteBuffer.wrap(md5);
		return new Token(byteBuffers.getInt(), byteBuffers.getInt(),byteBuffers.getInt(),byteBuffers.getInt()).asContextToken();
	}
	private static Token makeToken(Object ...objs){
		checkArgument(null != objs && 0 != objs.length,"objs must not be null or empty");
		StringBuffer buffer = new StringBuffer(64);
		for(Object obj :objs){
			buffer.append(obj);
		}
		return makeToken(buffer.toString().getBytes());
	}
	/**
	 * 计算设备令牌
	 * @param device 设备参数(包括设备ID,MAC地址,序列号),序列号为null则用MAC地址代替
	 * @return 设备访问令牌
	 * @throws IllegalArgumentException 设备ID,MAC地址为{@code null}
	 */
	private static Token makeDeviceTokenOf(DeviceBean device){
		checkArgument(null != device,"device is null");
		checkArgument(
						null != device.getId() 
				&& 	null != device.getMac(),
				"null device argument(id,mac)");
		return makeToken(device.getId(),device.getMac(),
				MoreObjects.firstNonNull(device.getSerialNo(),device.getMac()))
				.asDeviceToken(device.getId());
	}
	private static Token makePersonTokenOf(int personId){
		ByteBuffer buffer = ByteBuffer.wrap(new byte[8]);
		buffer.asLongBuffer().put(personId);
		return makeToken(buffer.array()).asPersonToken(personId);
	}
	private static Token makeRootToken(String password){
		ByteBuffer buffer = ByteBuffer.wrap(new byte[8]);
		buffer.asLongBuffer().put(System.currentTimeMillis());
		return makeToken(Bytes.concat(password.getBytes(),buffer.array())).asRootToken();
	}
	/**
	 * 从{@link #deviceTokenTable}删除指定设备的令牌
	 * @param deviceId
	 */
	private void removeDeviceTokenOf(int deviceId){
		deviceTokenTable.remove(Integer.toString(deviceId));
	}
	/**
	 * 从{@link #personTokenTable}删除指定人员的令牌
	 * @param personId
	 */
	private void removePersonTokenOf(int personId){
		personTokenTable.remove(Integer.toString(personId));
	}
	/**
	 * 如果{@code token}为设备令牌则返回对应的设备信息对象{@link DeviceBean},否则返回{@code null}
	 * @param token 令牌
	 * @return {@link DeviceBean}对象或{@code null}
	 */
	protected DeviceBean getDeviceOrNull(Token token){
		return (token != null && token.getType() == TokenType.DEVICE) ? dao.daoGetDevice(token.getId()) : null;
	}
	/**
	 * 如果{@code token}为人员令牌则返回对应的人员信息对象{@link PersonBean},否则返回{@code null}
	 * @param token 令牌
	 * @return {@link PersonBean}对象或{@code null}
	 */
	protected PersonBean getPersonOrNull(Token token){
		return (token != null && token.getType() == TokenType.PERSON) ? dao.daoGetPerson(token.getId()) : null;
	}
	/**
	 * 从令牌中获取人员等级
	 * @param token
	 */
	protected PersonRank rankOf(Token token){
		if(token != null && token.getType() == TokenType.ROOT){
			return PersonRank.root;
		}
		PersonBean personBean = getPersonOrNull(token);
		return personBean == null ? PersonRank.person : PersonRank.fromRank(personBean.getRank());
	}
	/**
	 * 人员令牌(token)的等级小于指定的等级(rank)时抛出异常
	 * @param token
	 * @param rank
	 * @throws ServiceSecurityException
	 */
	protected void checkRank(Token token,PersonRank rank) throws ServiceSecurityException{
		if(rankOf(token).ordinal() < checkNotNull(rank,"rank is null").ordinal()){
			throw new ServiceSecurityException("admin rank required",SecurityExceptionType.TOO_LOW_RANK);
		}
	}
	/**
	 * 设备注册
	 * @param newDevice
	 * @return 保存到数据库的 newDevice
	 * @throws ServiceSecurityException
	 */
	protected DeviceBean registerDevice(DeviceBean newDevice)
			throws ServiceSecurityException{
		TokenOp.REGISTER.asContextTokenOp();
		checkArgument(null != newDevice,"deviceBean must not be null");
		// 检查是否为新记录，
		checkArgument(newDevice.isNew(),
				"for device registeration the 'newDevice' must be a new record,so the _isNew field must be true ");
		// ID为自增长键，新记录ID字段不能指定，由数据库分配
		checkArgument(
				!newDevice.isModified(FL_DEVICE_ID_ID) 
				|| Objects.equal(0,newDevice.getId()),
				"for device registeration the 'newDevice' must be a new record,so id field must be not be set or be zero");
		// sdk_version字段不可为空
		checkArgument(!Strings.isNullOrEmpty(newDevice.getUsedSdks()), "sdkVersion must not be null or empty");

		// 检查sdk_version是否允许注册
		checkArgument(FEATURE_CONFIG.allValidSdkVersions(newDevice.getUsedSdks()), 
				"UNSUPPORTED SDK Version [%s]",newDevice.getUsedSdks());
		String mac = newDevice.getMac();
    	checkArgument(mac != null && mac.matches("^[\\da-fA-F]{12}$"),"INVALID mac address");
    	mac = mac.toLowerCase();
		DeviceBean dmac = this.dao.daoGetDeviceByIndexMac(mac);
		if(null !=dmac ){
			// 设备已经注册
			boolean oldSnValid = isValidSerialNo(dmac.getSerialNo());
			if(Objects.equal(newDevice.getSerialNo(),dmac.getSerialNo()) && oldSnValid){
				// 序列号一致且有效
				// DO NOTHING
			}else	if(null == newDevice.getSerialNo() && oldSnValid){
				// 原序列号有效就使用原序列号
				newDevice.setSerialNo(dmac.getSerialNo());
			}else{
				checkNotOccupiedSerialNo(newDevice.getSerialNo());
				checkValidSerialNo(newDevice.getSerialNo());
				// 用新序列号替换原记录中无效的序列号
			}
			// 复制新记录中的常量字段
			dmac.copy(newDevice, CONST_FIELDS);			
			return dmac.beModified() ? dao.daoSaveDevice(dmac):dmac;
		}else{
			checkNotOccupiedSerialNo(newDevice.getSerialNo());
			checkValidSerialNo(newDevice.getSerialNo());
			return this.dao.daoSaveDevice(newDevice);
		}
	}
	/**
	 * 设备注销
	 * @param deviceId
	 * @throws ServiceSecurityException
	 */
	protected void unregisterDevice(int deviceId)
			throws ServiceSecurityException{
		TokenOp.UNREGISTER.asContextTokenOp();
		this.dao.daoDeleteDevice(deviceId);
	}
	/**
	 * 申请设备令牌
	 * @param loginDevice 申请信息设备信息，必须提供{@code id, mac, serialNo}字段
	 * @throws ServiceSecurityException
	 */
	protected Token applyDeviceToken(DeviceBean loginDevice)
			throws ServiceSecurityException{
		TokenOp.APPLY.asContextTokenOp();
		checkValidDeviceId(loginDevice.getId());

		DeviceBean device = dao.daoGetDevice(loginDevice.getId());
		if(!Objects.equal(device.getMac(), loginDevice.getMac()) ){
			throw new ServiceSecurityException(
					String.format("MISMATCH MAC:%s", device.getMac()))
				.setType(SecurityExceptionType.INVALID_MAC);
		}
		if(!Objects.equal(device.getSerialNo(), loginDevice.getSerialNo())){
			throw new ServiceSecurityException(
					String.format("MISMATCH Serial Number:%s", device.getSerialNo()))
				.setType(SecurityExceptionType.INVALID_SN);
		}
		// 生成一个新令牌
		Token token = makeDeviceTokenOf(device);
		deviceTokenTable.set(device.getId().toString(), token, false);
		return token;
	}
	/**
	 * 释放设备令牌
	 * @param token 当前持有的令牌
	 * @throws ServiceSecurityException
	 */
	protected void releaseDeviceToken(Token token)
			throws ServiceSecurityException{
		TokenOp.RELEASE.asContextTokenOp();;
		Enable.DEVICE_ONLY.check(this, token);
		removeDeviceTokenOf(token.getId());
	}
	/**
	 * 申请人员访问令牌
	 * @param personId
	 * @param password 
	 * @param isMd5 
	 * @throws ServiceSecurityException
	 * @see #checkValidPassword(String, String, boolean)
	 */
	protected Token applyPersonToken(int personId, String password, boolean isMd5)
			throws ServiceSecurityException{
		TokenOp.APPLY.asContextTokenOp();
		checkValidPassword(Integer.toString(personId), password, isMd5);
		if(CommonConstant.PersonRank.person.equals(CommonConstant.PersonRank.fromRank(dao.daoGetPerson(personId).getRank()))
			&&	rejectZero ){
			// 当配置参数指定不允许普通人员申请令牌时抛出异常
			throw new ServiceSecurityException(
					String.format("REJECTION OF APPLICATION for rank 0 user (id = %d)",personId))
				.setType(SecurityExceptionType.REJECT_APPLY);
		}
		Token token = makePersonTokenOf(personId);
		String key = Integer.toString(personId);
		personTokenTable.set(key, token, false);
		personTokenTable.expireValue(token);
		return token;
	}
	/**
	 * 释放人员访问令牌
	 * @param token 当前持有的令牌
	 * @throws ServiceSecurityException
	 */
	protected void releasePersonToken(Token token)
			throws ServiceSecurityException{
		TokenOp.RELEASE.asContextTokenOp();

		Enable.PERSON_ONLY.check(this, token);
		removePersonTokenOf(token.getId());
	}
	/**
	 * 申请root访问令牌
	 * @param password root密码
	 * @param isMd5 为{@code false}代表{@code password}为明文,{@code true}指定{@code password}为32位MD5密文(小写)
	 * @throws ServiceSecurityException
	 */
	protected Token applyRootToken(String password, boolean isMd5)
			throws ServiceSecurityException{
		TokenOp.APPLY.asContextTokenOp();
		checkValidPassword(ROOT_NAME,password,isMd5);
		Token token = makeRootToken(password);
		String key = Integer.toString(token.getId());
		personTokenTable.set(key, token, false);
		personTokenTable.expireValue(token);
		return token;
	}
	/**
	 * 释放root访问令牌
	 * @param token 当前持有的令牌
	 * @throws ServiceSecurityException
	 */
	protected void releaseRootToken(Token token)
			throws ServiceSecurityException{
		TokenOp.RELEASE.asContextTokenOp();

		Enable.ROOT_ONLY.check(this, token);
		removePersonTokenOf(token.getId());
	}
	/**
	 * 验证用户密码是否匹配
	 * @param userId 用户id字符串,root用户id即为{@link CommonConstant#ROOT_NAME}
	 * @param password 用户密码
	 * @param isMd5 为{@code false}代表{@code password}为明文,{@code true}指定{@code password}为32位MD5密文(小写)
	 * @throws RuntimeDaoException 
	 * @throws ServiceSecurityException 
	 * @throws IllegalArgumentException {@code userId} 无效
	 */
	protected boolean isValidPassword(String userId,String password, boolean isMd5) throws RuntimeDaoException, ServiceSecurityException {
		TokenOp.VALIDPWD.asContextTokenOp();
		checkArgument(!Strings.isNullOrEmpty(userId),"INVALID argument,must not be null or empty");
		if(ROOT_NAME.equals(userId)){
			// 从配置文件中读取root密码算出MD5与输入的密码比较
			return cg.cryptograph(CONFIG.getString(ROOT_PASSWORD),false).equals(cg.cryptograph(password,isMd5));
		}else{
			// 从数据库中读取用户密码(已经掺盐加密)与输入的密码比较
			try{
				Integer id = Integer.valueOf(userId);
				String passwordMd5InDb = dao.daoGetPersonChecked(id).getPassword();
				return cg.cryptograph(password,isMd5).equals(passwordMd5InDb);
			}catch(ObjectRetrievalException e){
				throw new ServiceSecurityException(SecurityExceptionType.INVALID_PERSON_ID);
			}catch(NumberFormatException e){
				throw new ServiceSecurityException(SecurityExceptionType.INVALID_PERSON_ID);
			}
		}
	}
	/**
	 * 检查密码是否正确
	 * @param userId
	 * @param password
	 * @param isMd5
	 * @throws ServiceSecurityException 密码不匹配,{@code userId}无效
	 * @see #isValidPassword(String, String, boolean)
	 */
	protected void checkValidPassword(String userId,String password, boolean isMd5) throws ServiceSecurityException{
		if(!isValidPassword(userId, password, isMd5)){
			throw new ServiceSecurityException(
					String.format("INVALID password [%s]for user [%s]",password,userId))
				.setType(SecurityExceptionType.INVALID_PASSWORD);
		}
	}
	/** 
	 * 申请一个唯一的命令序列号 
	 * @param id
	 */
	protected int applyCmdSn(int id){
		int sn = JedisUtils.incr(KEY_CMD_SN);
		String key = Long.toString(sn);
		this.cmdSnTable.set(key, id, false);
		this.cmdSnTable.expire(key);
		return sn;
	}
	/** 申请一个唯一的命令响应通道 
	 * @param id
	 * @param duration 通道有效时间(秒) 大于0有效,否则使用默认的有效期*/
	protected String applyAckChannel(int id, int duration){
		String channel = new StringBuffer(ACK_PREFIX)
				.append(JedisUtils.incr(KEY_ACK_SN))
				.toString();
		this.ackChannelTable.set(channel, id, false);
		if(duration>0){
			this.ackChannelTable.expire(channel,duration,TimeUnit.SECONDS);
		}else{
			this.ackChannelTable.expire(channel);
		}
		return channel;
	}
	
	/**
	 * 判断命令序列号是否有效
	 * @param cmdSn
	 */
	protected boolean isValidCmdSn(int cmdSn){
		return this.cmdSnTable.containsKey(Integer.toString(cmdSn));
	}
	final Predicate<Integer> cmdSnValidator = new Predicate<Integer>(){

		@Override
		public boolean apply(Integer input) {
			return isValidCmdSn(input);
		}};

	/**
	 * 判断命令响应通道是否有效
	 * @param ackChannel
	 */
	protected boolean isValidAckChannel(String ackChannel){
		return this.ackChannelTable.containsKey(ackChannel);
	}

//	protected void checkNonNullField(BaseRow bean, int... columnIds) throws ServiceSecurityException{
//		checkSecurity(!dao.hasNullField(bean, columnIds), SecurityExceptionType.ACCESS_DENIED, "missing required column");
//	}
	
	private static String formatWhere(String where, String column, List<Integer>nodes){
		where = Strings.nullToEmpty(where).trim();
		if(where.isEmpty()){
			where = "WHERE ";
		}else{
			where = where.concat(" AND ");
		}
		if(null == nodes || nodes.isEmpty()){
			// 为空或null则用一个无效值代替,确保WHERE column IN()语句语法正确
			nodes = Arrays.asList(-1);
		}
		return where +  column + " IN(" + Joiner.on(',').join(nodes) + ")";		
	}
	private List<Integer> getPersonNodes(Token token){

		List<Integer> personGroups = getPersonGroupNodes(token);
		/** fl_person.group_id */
		String column =FL_PERSON_FULL_FIELDS_LIST.get(FL_PERSON_ID_GROUP_ID);
		return dao.daoLoadPersonIdByWhere(formatWhere(null, column, personGroups));
	}
	private List<Integer> getPersonGroupNodes(Token token){
		PersonGroupBean topGroup;
		switch(token.getType()){
		case DEVICE:{
			DeviceGroupBean top = dao.topOfParentForDeviceGroup(dao.daoGetGroupOfDevice(token.getId()));
			topGroup = dao.daoGetPersonGroup(top.getRootGroup());
			break;
		}
		case PERSON:{
			topGroup = dao.topOfParentForPersonGroup(dao.daoGetGroupOfPerson(token.getId()));
			break;
		}
		default:
			return Collections.emptyList();
		}
		
		return dao.daoToPrimaryKeyListFromPersonGroups(dao.daoChildListByParentForPersonGroup(topGroup));
	}
	private List<Integer> getDeviceGroupNodes(Token token){
		DeviceGroupBean topGroup;
		switch(token.getType()){
		case DEVICE:{
			topGroup = dao.topOfParentForDeviceGroup(dao.daoGetGroupOfDevice(token.getId()));
			break;
		}
		case PERSON:{
			PersonGroupBean top = dao.topOfParentForPersonGroup(dao.daoGetGroupOfPerson(token.getId()));
			topGroup = dao.daoGetDeviceGroup(top.getRootGroup());
			break;
		}
		default:
			return Collections.emptyList();
		}
		
		return  dao.daoToPrimaryKeyListFromDeviceGroups(dao.daoChildListByParentForDeviceGroup(topGroup));
	}
	protected String normailizeWhereForPerson(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_person.group_id */
		String column = FL_PERSON_FULL_FIELDS_LIST.get(FL_PERSON_ID_GROUP_ID);
		List<Integer> nodes = getPersonGroupNodes(token);
		return formatWhere(where,column,nodes);
	}
	
	protected String normailizeWhereForPersonGroup(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_person_group.id */
		String column = FL_PERSON_GROUP_FULL_FIELDS_LIST.get(FL_PERSON_GROUP_ID_ID);
		List<Integer> nodes = getPersonGroupNodes(token);
		return formatWhere(where,column,nodes);
	}
	
	protected String normailizeWhereForDevice(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		List<Integer> nodes = getDeviceGroupNodes(token);
		/** fl_device.group_id */
		String column = FL_DEVICE_FULL_FIELDS_LIST.get(FL_DEVICE_ID_GROUP_ID);
		return formatWhere(where,column,nodes);
	}
	protected String normailizeWhereForDeviceGroup(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_device_group.id */
		String column = FL_DEVICE_GROUP_FULL_FIELDS_LIST.get(FL_DEVICE_GROUP_ID_ID);
		List<Integer> nodes = getDeviceGroupNodes(token);
		return formatWhere(where,column,nodes);
	}
	protected String normailizeWhereForLog(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_log.person_id */
		String column = FL_LOG_FULL_FIELDS_LIST.get(FL_LOG_ID_PERSON_ID);
		List<Integer> nodes = getPersonNodes(token);
		return formatWhere(where,column,nodes);
	}
	protected String normailizeWhereForLogLight(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_log_light.person_id */
		String column = FL_LOG_LIGHT_FULL_FIELDS_LIST.get(FL_LOG_LIGHT_ID_PERSON_ID);
		List<Integer> nodes = getPersonNodes(token);
		return formatWhere(where,column,nodes);
	}
	protected String normailizeWhereForFeature(Token token,String where){
		if(!checkPermission || TokenType.ROOT.equals(token.getType())){
			return where;
		}
		/** fl_feature.person_id */
		String column = FL_FEATURE_FULL_FIELDS_LIST.get(FL_FEATURE_ID_PERSON_ID);
		List<Integer> nodes = getPersonNodes(token);
		return formatWhere(where,column,nodes);
	}
	/**
	 * 令牌权限检查,如果指定的设备或用户无权对(person表)target操作则抛出异常
	 * @param token 设备令牌
	 * @param target person表操作目标:PK(Integer),PapersNum(String),PersonBean,or container ,array with this element type 
	 * @throws ServiceSecurityException 没有操作权限
	 */
	protected void checkPermissionForPerson(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof Integer){
			switch(token.getType()){
			case DEVICE:
				checkPermissionForPerson(
						dao.daoGetGroupOfDevice(token.getId()),
						dao.daoGetGroupOfPerson((Integer)target));
				break;
			case PERSON:
				checkPermissionForPerson(
						dao.daoGetGroupOfPerson(token.getId()),
						dao.daoGetGroupOfPerson((Integer)target));
				break;
			default:
				break;
			}
		}else if(target instanceof String){
			// target 为证件号码
			checkPermissionForPerson(token,dao.daoGetPersonByIndexPapersNum((String)target));
		}else if(target instanceof PersonBean){
			PersonBean bean = (PersonBean)target;
			Integer id = bean.getId();
			checkPermissionForPerson(token,id);
			if(!Objects.equal(dao.daoGetGroupOfPerson(id), dao.daoGetPersonGroup(bean.getGroupId()))){
				checkPermissionForPersonGroup(token, bean.getGroupId());
			}
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForPerson(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForPerson(token,e);
			}
		}
	}
	/**
	 * 令牌权限检查,如果指定的设备或用户无权对(person表)target操作则抛出异常
	 * @param token 设备令牌
	 * @param target person表操作目标:Mobile(String),or container ,array with this element type 
	 * @throws ServiceSecurityException 没有操作权限
	 */
	protected void checkPermissionForPersonByMobilePhone(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof String){
			// target 为证件号码
			checkPermissionForPerson(token,dao.daoGetPersonByIndexMobilePhone((String)target));
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForPersonByMobilePhone(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForPersonByMobilePhone(token,e);
			}
		}
	}
	/**
	 * 令牌权限检查,如果指定的设备或用户无权对(person_group表)target操作则抛出异常
	 * @param token 设备令牌
	 * @param target person_group表操作目标:PK(Integer),PersonGroupBean,or container ,array with this element type 
	 * @throws ServiceSecurityException 没有操作权限
	 */
	protected void checkPermissionForPersonGroup(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof Integer){
			switch(token.getType()){
			case DEVICE:
				checkPermissionForPerson(
						dao.daoGetGroupOfDevice(token.getId()),
						dao.daoGetPersonGroup((Integer)target));
				break;
			case PERSON:
				checkPermissionForPerson(
						dao.daoGetGroupOfPerson(token.getId()),
						dao.daoGetPersonGroup((Integer)target));
				break;
			default:
				break;
			}
		}else if(target instanceof PersonGroupBean){
			PersonGroupBean bean = (PersonGroupBean)target;
			Integer id = bean.getId();
			checkPermissionForPersonGroup(token,id);
			if(!Objects.equal(dao.daoGetParentOfPersonGroup(id), dao.daoGetPersonGroup(bean.getParent()))){
				checkPermissionForPersonGroup(token, bean.getParent());
			}
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForPersonGroup(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForPersonGroup(token,e);
			}
		}
	}
	/**
	 * 令牌权限检查,如果指定的设备或用户无权对(device表)target操作则抛出异常
	 * @param token 设备令牌
	 * @param target device表操作目标:PK(Integer),DeviceBean,or container ,array with this element type 
	 * @throws ServiceSecurityException 没有操作权限
	 */
	protected void checkPermissionForDevice(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof Integer){
			switch(token.getType()){
			case DEVICE:
				checkPermissionForDevice(
						dao.daoGetGroupOfDevice(token.getId()),
						dao.daoGetGroupOfDevice((Integer)target));
				break;
			case PERSON:
				checkPermissionForDevice(
						dao.daoGetGroupOfPerson(token.getId()),
						dao.daoGetGroupOfDevice((Integer)target));
				break;
			default:
				break;
			}
		}else if(target instanceof String){
			// target 为MAC地址
			checkPermissionForDevice(token,dao.daoGetDeviceByIndexMac((String)target));
		}else if(target instanceof DeviceBean){
			DeviceBean bean = (DeviceBean)target;
			Integer id = bean.getId();
			checkPermissionForDevice(token,id);
			if(!Objects.equal(dao.daoGetGroupOfDevice(id), dao.daoGetDeviceGroup(bean.getGroupId()))){
				checkPermissionForDeviceGroup(token, bean.getGroupId());
			}	
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForDevice(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForDevice(token,e);
			}
		}
	}
	/**
	 * 令牌权限检查,如果指定的设备或用户无权对(device_group表)target操作则抛出异常
	 * @param token 设备令牌
	 * @param target device_group表操作目标:PK(Integer),DeviceGroupBean,or container ,array with this element type 
	 * @throws ServiceSecurityException 没有操作权限
	 */
	protected void checkPermissionForDeviceGroup(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof Integer){
			switch(token.getType()){
			case DEVICE:
				checkPermissionForDevice(
						dao.daoGetGroupOfDevice(token.getId()),
						dao.daoGetDeviceGroup((Integer)target));
				break;
			case PERSON:
				checkPermissionForDevice(
						dao.daoGetGroupOfPerson(token.getId()),
						dao.daoGetDeviceGroup((Integer)target));
				break;
			default:
				break;
			}
		}else if(target instanceof DeviceGroupBean){
			DeviceGroupBean bean = (DeviceGroupBean)target;
			Integer id = bean.getId();
			checkPermissionForDeviceGroup(token,id);
			if(!Objects.equal(dao.daoGetParentOfDeviceGroup(id), dao.daoGetDeviceGroup(bean.getParent()))){
				checkPermissionForDeviceGroup(token, bean.getParent());
			}
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForDeviceGroup(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForDeviceGroup(token,e);
			}
		}
	}
	protected void checkPermissionForFeature(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof String){
			FeatureBean featureBean = dao.daoGetFeature((String)target);
			if(null != featureBean){
				checkPermissionForPerson(token,featureBean.getPersonId());
			}
		}else if(target instanceof FeatureBean){
			checkPermissionForFeature(token,((FeatureBean)target).getMd5());
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForFeature(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForFeature(token,e);
			}
		}
	}

	protected void checkPermissionForImage(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof String){
			List<String> featureIds = dao.daoGetFeaturesOfImage((String)target);
			if(!featureIds.isEmpty()){
				FeatureBean featureBean = dao.daoGetFeature(featureIds.get(0));
				if(null != featureBean){
					checkPermissionForFeature(token,featureBean.getMd5());
				}
			}
		}else if(target instanceof ImageBean){
			checkPermissionForImage(token,((ImageBean)target).getMd5());
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForImage(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForImage(token,e);
			}
		}
	}
	protected void checkPermissionForLog(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof LogBean){
			checkPermissionForPerson(token,((LogBean)target).getPersonId());
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForLog(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e:(Object[])target){
				checkPermissionForLog(token,e);
			}
		}
	}
	protected void checkPermissionForPermit(Token token,Object target) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(target instanceof PermitBean){
			checkPermissionForPersonGroup(token,((PermitBean)target).getPersonGroupId());
			checkPermissionForDeviceGroup(token,((PermitBean)target).getDeviceGroupId());
		}else if(target instanceof Iterable<?>){
			for(Iterator<?> itor = ((Iterable<?>)target).iterator() ; itor.hasNext() ; ){
				checkPermissionForPermit(token,itor.next());
			}
		}else if(target instanceof Object[]){
			for(Object e: (Object[])target){
				checkPermissionForPermit(token,e);
			}
		}
	}
	protected void checkPermissionForTmpPwd(Token token,int targetId,TmpPwdTargetType targetType) throws ServiceSecurityException{
		if(TokenType.ROOT.equals(token.getType())){
			return;
		}
		if(null == targetType){
			return;
		}
		switch (targetType) {
		case USER_GROUP_ID:
			checkPermissionForPersonGroup(token, targetId);
			break;
		case USER_ID:
			checkPermissionForPerson(token, targetId);
		case DEVICE_GROUP_ID:
			checkPermissionForDeviceGroup(token, targetId);
		case DEVICE_ID:
			checkPermissionForDevice(token, targetId);
		default:
			break;
		}
	}
	private void checkPermissionForPerson(DeviceGroupBean operator,PersonGroupBean target) throws ServiceSecurityException{
		if(!checkPermission || null == operator || null == target){
			return ;
		}
		DeviceGroupBean operatorTop = dao.topOfParentForDeviceGroup(operator);
		PersonGroupBean targetTop = dao.topOfParentForPersonGroup(target);
		if(Objects.equal(DEFAULT_GROUP_ID , targetTop.getId())){
			// 允许操作默认组
			return ;
		}
		// 检查是否在同一个管理边界
		checkSecurity(Objects.equal(operatorTop.getRootGroup(),targetTop.getId()) && Objects.equal(targetTop.getRootGroup(),operatorTop.getId()),
				SecurityExceptionType.ACCESS_DENIED,
				"operator(device) group %s(%s) not permission to operate target(person) group %s(%s)",
				operator.getId(), operator.getName(), target.getId(), target.getName());
		
	}
	private void checkPermissionForPerson(PersonGroupBean operator,PersonGroupBean target) throws ServiceSecurityException{
		if(!checkPermission || null == operator || null == target){
			return ;
		}
		PersonGroupBean operatorTop = dao.topOfParentForPersonGroup(operator);
		PersonGroupBean targetTop = dao.topOfParentForPersonGroup(target);
		if(Objects.equal(DEFAULT_GROUP_ID , targetTop.getId())){
			// 允许操作默认组
			return ;
		}
		// 检查是否在同一个管理边界
		checkSecurity(Objects.equal(operatorTop.getId(),targetTop.getId()),
				SecurityExceptionType.ACCESS_DENIED,
				"operate(person) group %s(%s) not belong to savme top with target(person) group %s(%s)",
				operator.getId(), operator.getName(), target.getId(), target.getName());

	}
	
	private void checkPermissionForDevice(PersonGroupBean operator,DeviceGroupBean target) throws ServiceSecurityException{
		if(!checkPermission || null == operator || null == target){
			return ;
		}
		PersonGroupBean operatorTop = dao.topOfParentForPersonGroup(operator);
		DeviceGroupBean  targetTop = dao.topOfParentForDeviceGroup(target);
		if(Objects.equal(DEFAULT_GROUP_ID , targetTop.getId())){
			// 允许操作默认组
			return ;
		}
		// 检查是否在同一个管理边界
		checkSecurity(Objects.equal(operatorTop.getRootGroup(),targetTop.getId()) && Objects.equal(targetTop.getRootGroup(),operatorTop.getId()),
				SecurityExceptionType.ACCESS_DENIED,
				"operator(person) group %s(%s) not permission to operate target(device) group %s(%s)",
				operator.getId(), operator.getName(), target.getId(), target.getName());
		
	}
	private void checkPermissionForDevice(DeviceGroupBean operator,DeviceGroupBean target) throws ServiceSecurityException{
		if(!checkPermission || null == operator || null == target){
			return ;
		}
		DeviceGroupBean operatorTop = dao.topOfParentForDeviceGroup(operator);
		DeviceGroupBean targetTop = dao.topOfParentForDeviceGroup(target);
		if(Objects.equal(DEFAULT_GROUP_ID , operator.getId())){
			// 允许操作者在默认组
			return ;
		}
		if(Objects.equal(DEFAULT_GROUP_ID , targetTop.getId())){
			// 允许操作默认组
			return ;
		}
		// 检查是否在同一个管理边界
		checkSecurity(Objects.equal(operatorTop.getId(),targetTop.getId()),
				SecurityExceptionType.ACCESS_DENIED,
				"operate(device) group %s(%s) not belong to savme top with target(device) group %s(%s)",
				operator.getId(), operator.getName(), target.getId(), target.getName());
	}

}
